package top.ezzz.search;


import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.*;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import top.ezzz.search.exception.NoExistException;
import top.ezzz.search.exception.RequestException;
import top.ezzz.search.model.EsAliases;
import top.ezzz.search.model.EsSetting;

import java.rmi.ServerException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.function.Consumer;

/**
 * ElasticSearch 执行上下文（支持 7.x +）
 *
 * @author noear
 * @since 1.0
 */
public class EsContext {
    private final String[] urls;
    private int urlIndex;
    private final String username;
    private final String password;

    private String meta;
    private int version = 0;

    /**
     * 获取元信息
     */
    public String getMeta() {
        return meta;
    }

    /**
     * 获取版本号
     */
    public int getVersion() {
        return version;
    }


    public EsContext(Properties prop) {
        String url = prop.getProperty("url");
        String passwordStr = prop.getProperty("password");
        this.username = prop.getProperty("username");
        this.password = passwordStr;

        List<String> urlAry = new ArrayList<>();
        for (String ser : url.split(",")) {
            if (ser.contains("://")) {
                urlAry.add(ser);
            } else {
                urlAry.add("http://" + ser);
            }
        }
        this.urls = urlAry.toArray(new String[0]);
        this.initMeta();
    }

    public EsContext(String url) {
        this(url, null, null);
    }

    public EsContext(String url, String username, String password) {
        this.username = username;
        this.password = password;

        List<String> urlAry = new ArrayList<>();
        for (String ser : url.split(",")) {
            if (ser.contains("://")) {
                urlAry.add(ser);
            } else {
                urlAry.add("http://" + ser);
            }
        }
        this.urls = urlAry.toArray(new String[0]);
        this.initMeta();
    }

    /**
     * 初始化元信息
     */
    private void initMeta() {
        try {
            this.meta = getHttp("").execute().body();
            if (StrUtil.isEmpty(meta)) {
                return;
            }
            JSONObject obj = JSONUtil.parseObj(meta);
            String verString = obj.getByPath("version.number", String.class);
            if (StrUtil.isEmpty(verString)) {
                return;
            }
            String varMain = verString.split("\\.")[0];
            this.version = Integer.parseInt(varMain);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private String getUrl() {
        if (urls.length == 0) {
            return urls[0];
        } else {
            if (urlIndex > 10000000) {
                urlIndex = 0;
            }
            return urls[urlIndex % urls.length];
        }
    }

    protected HttpRequest getHttp(String path) {
        HttpRequest http = HttpUtil.createGet(getUrl() + path);
        if (StrUtil.isNotEmpty(username)) {
            String token = Base64.encode(username + ":" + password);
            String auth = "Basic " + token;
            http.header("Authorization", auth);
        }
        return http;
    }

    /**
     * 执行并返回结果体
     *
     * @param cmd 命令
     */
    public String execAsBody(EsCommand cmd) throws HttpException {
        HttpResponse response;
        EsCommandHolder holder = new EsCommandHolder(this, cmd);
        EsGlobal.applyCommandBefore(holder);
        long start = System.currentTimeMillis();
        if (StrUtil.isEmpty(cmd.dsl)) {
            response = getHttp(cmd.path).method(Method.valueOf(cmd.method)).timeout(cmd.timeout).execute();
        } else {
            response = getHttp(cmd.path).body(cmd.dsl, cmd.dslType).timeout(cmd.timeout)
                    .method(Method.valueOf(cmd.method)).execute();
        }
        String body = response.body();
        int code = response.getStatus();
        holder.setTimespan(System.currentTimeMillis() - start);
        EsGlobal.applyCommandAfter(holder);
        if (code >= 200 && code <= 300) {
            return body;
        } else if (code == 404) {
            throw new NoExistException(body);
        } else if (code >= 500) {
            throw new RequestException(body);
        } else {
            throw new HttpException(body);
        }
    }

    /**
     * 执行并返回状态码
     *
     * @param cmd 命令
     */
    public int execAsCode(EsCommand cmd) throws HttpException {
        int code;
        EsCommandHolder holder = new EsCommandHolder(this, cmd);
        EsGlobal.applyCommandBefore(holder);
        long start = System.currentTimeMillis();
        if (StrUtil.isEmpty(cmd.dsl)) {
            code = getHttp(cmd.path).timeout(cmd.timeout).method(Method.valueOf(cmd.method)).execute().getStatus();
        } else {
            code = getHttp(cmd.path).body(cmd.dsl, cmd.dslType).timeout(cmd.timeout)
                    .method(Method.valueOf(cmd.method)).execute().getStatus();
        }
        holder.setTimespan(System.currentTimeMillis() - start);
        EsGlobal.applyCommandAfter(holder);
        return code;
    }

    /**
     * 获取索引操作
     */
    public EsQuery indice(String indiceName) {
        return new EsQuery(this, indiceName, false);
    }

    /**
     * 索引创建
     *
     * @param indiceName 索引名字
     */
    public String indiceCreate(String indiceName, String dsl) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.PUT.name();
        cmd.path = String.format("/%s", indiceName);
        cmd.dsl = dsl;
        cmd.dslType = ContentType.JSON.getValue();
        return execAsBody(cmd);
    }

    /**
     * 索引是否存在
     *
     * @param indiceName 索引名字
     */
    public boolean indiceExist(String indiceName) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.HEAD.name();
        cmd.path = String.format("/%s", indiceName);
        int code = execAsCode(cmd);
        return code == 200; //404不存在
    }

    /**
     * 索引删除
     *
     * @param indiceName 索引名字
     */
    public boolean indiceDrop(String indiceName) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.DELETE.name();
        cmd.path = String.format("/%s", indiceName);
        try {
            execAsBody(cmd);
            return true;
        } catch (NoExistException e) {
            return true;
        }
    }

    /**
     * 索引设置修改
     *
     * @param indiceName 索引名字
     */
    public String indiceSettings(String indiceName, Consumer<EsSetting> setting) throws HttpException {
        JSONObject obj = JSONUtil.createObj();
        EsSetting s = new EsSetting(obj);
        setting.accept(s);
        EsCommand cmd = new EsCommand();
        cmd.method = Method.PUT.name();
        cmd.path = String.format("/%s/_settings", indiceName);
        cmd.dsl = obj.toString();
        return execAsBody(cmd);
    }

    /**
     * 索引结构显示
     *
     * @param indiceName 索引名字
     */
    public String indiceShow(String indiceName) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.GET.name();
        cmd.path = String.format("/%s", indiceName);
        return execAsBody(cmd);
    }

    /////////////////

    /**
     * 索引别名处理
     */
    public String indiceAliases(Consumer<EsAliases> aliases) throws HttpException {
        JSONObject obj = JSONUtil.createObj();
        EsAliases e = new EsAliases(obj);
        aliases.accept(e);
        JSONObject oNode = JSONUtil.createObj().set("actions", obj);
        EsCommand cmd = new EsCommand();
        cmd.method = Method.POST.name();
        cmd.dslType = ContentType.JSON.getValue();
        cmd.dsl = oNode.toString();
        cmd.path = "/_aliases";
        return execAsBody(cmd);
    }

    //=======================

    public EsQuery stream(String streamName) {
        return new EsQuery(this, streamName, true);
    }


    /**
     * 模板创建
     */
    public String templateCreate(String templateName, String dsl) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.PUT.name();
        cmd.path = String.format("/_index_template/%s", templateName);
        cmd.dsl = dsl;
        cmd.dslType = ContentType.JSON.getValue();
        return execAsBody(cmd);
    }

    /**
     * 模板是否创建
     *
     * @param templateName 模板名称
     */
    public boolean templateExist(String templateName) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.HEAD.name();
        ;
        cmd.path = String.format("/_index_template/%s", templateName);
        int code = execAsCode(cmd);
        return code == 200; //404不存在
    }

    /**
     * 模板结构显示
     *
     * @param templateName 模板名称
     */
    public String templateShow(String templateName) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.GET.name();
        ;
        cmd.path = String.format("/_index_template/%s", templateName);
        return execAsBody(cmd);
    }

    /**
     * 模板创建
     */
    public String componentCreate(String componentName, String dsl) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.PUT.name();
        ;
        cmd.path = String.format("/_component_template/%s", componentName);
        cmd.dsl = dsl;
        cmd.dslType = ContentType.JSON.getValue();
        return execAsBody(cmd);
    }

    /**
     * 模板是否创建
     *
     * @param componentName 模板名称
     */
    public boolean componentExist(String componentName) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.HEAD.name();
        ;
        cmd.path = String.format("/_component_template/%s", componentName);
        int code = execAsCode(cmd);
        return code == 200; //404不存在
    }

    /**
     * 模板结构创建
     *
     * @param componentName 模板名称
     */
    public String componentShow(String componentName) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.GET.name();
        ;
        cmd.path = String.format("/_component_template/%s", componentName);
        return execAsBody(cmd);
    }

    /**
     * 索引生命周期策略创建
     *
     * @param policyName 策略名称
     */
    public String policyCreate(String policyName, String dsl) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.PUT.name();
        ;
        cmd.path = String.format("/_ilm/policy/%s", policyName);
        cmd.dsl = dsl;
        cmd.dslType = ContentType.JSON.getValue();
        return execAsBody(cmd);
    }

    /**
     * 索引生命周期策略是否存在？
     *
     * @param policyName 策略名称
     */
    public boolean policyExist(String policyName) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.GET.name();
        ; //用 head 会出错
        cmd.path = String.format("/_ilm/policy/%s", policyName);
        int code = execAsCode(cmd);
        return code == 200; //404不存在
    }

    /**
     * 索引生命结构显示
     *
     * @param policyName 策略名称
     */
    public String policyShow(String policyName) throws HttpException {
        EsCommand cmd = new EsCommand();
        cmd.method = Method.GET.name();
        ;
        cmd.path = String.format("/_ilm/policy/%s", policyName);
        return execAsBody(cmd);
    }
}